#include "color/truecolor.h"
#include "lcms2.h"

using namespace std;
using namespace anip;

const string Path_sRGBProfile = "icc//sRGBv4pref.icc";

struct TrueColor::Impl
{
    static cmsContext defaultContext;
    static cmsHPROFILE sRGB, LAB4, null;
    //static cmsHANDLE
    //    ciecam02, // not yet in use
    //    gbd_sRGB;
    static bool initialized;

    static const unsigned int tfFlags = cmsFLAGS_COPY_ALPHA;

    static std::vector<cmsHTRANSFORM> transforms;
};

cmsContext TrueColor::Impl::defaultContext = nullptr;
cmsHPROFILE TrueColor::Impl::sRGB = nullptr;
cmsHPROFILE TrueColor::Impl::LAB4 = nullptr;
cmsHPROFILE TrueColor::Impl::null = nullptr;
//cmsHANDLE TrueColor::Impl::ciecam02 = nullptr;
//cmsHANDLE TrueColor::Impl::gbd_sRGB = nullptr;
std::vector<cmsHTRANSFORM> TrueColor::Impl::transforms;

bool TrueColor::Impl::initialized = false;

TrueColor::TrueColor(const color& srgb)
{
    from_sRGB(&srgb, this);
}

void _cmsLogger(cmsContext contextID, cmsUInt32Number code, const char* text)
{
    throw runtime_error("lcms error "s + to_string(code) + ": "s + string(text));
}

void TrueColor::init()
{
    if (Impl::initialized) return;

    Impl::defaultContext = cmsCreateContext(NULL, NULL);
    cmsSetLogErrorHandlerTHR(Impl::defaultContext, _cmsLogger);

    //Impl::sRGB = cmsOpenProfileFromFileTHR(
    //    Impl::defaultContext, Path_sRGBProfile.c_str(), "r");

    // seems like lcms's builtin sRGB is faster though has no intents,
    // but we don't use intents anyway
    Impl::sRGB = cmsCreate_sRGBProfileTHR(Impl::defaultContext);
    Impl::LAB4 = cmsCreateLab4ProfileTHR(Impl::defaultContext, cmsD50_xyY());
    Impl::null = cmsCreateNULLProfileTHR(Impl::defaultContext);

    //Impl::gbd_sRGB = cmsGBDAlloc(Impl::defaultContext);

    //// ... not sure that I used this in the right way, though
    //// must use an interval of 5 to get a decent speed
    //for (int r = 0; r <= 255; r += 5)
    //    for (int g = 0; g <= 255; g += 5)
    //        for (int b = 0; b <= 255; b += 5)
    //        {
    //            TrueColor col;
    //            from_sRGB(&color{ r / 255.0f, g / 255.0f, b / 255.0f, 1 }, &col);
    //            cmsGDBAddPoint(Impl::gbd_sRGB,
    //                &(cmsCIELab{
    //                    (cmsFloat64Number)col.L,
    //                    (cmsFloat64Number)col.a,
    //                    (cmsFloat64Number)col.b }));
    //        }
    //cmsGDBCompute(Impl::gbd_sRGB, 0);

    Impl::initialized = true;
}

void TrueColor::finalize()
{
    //cmsGBDFree(Impl::gbd_sRGB);

    cmsCloseProfile(Impl::sRGB);
    cmsCloseProfile(Impl::LAB4);

    for (auto t : Impl::transforms)
        cmsDeleteTransform(t);

    cmsDeleteContext(Impl::defaultContext);
    
    Impl::initialized = false;
}

TrueColor TrueColor::getBlackbodyChromaticities(double temperature)
{
    cmsCIExyY xyy{};
    cmsCIEXYZ xyz{};
    cmsCIELab lab{};
    cmsWhitePointFromTemp(&xyy, (cmsFloat64Number)temperature);
    cmsxyY2XYZ(&xyz, &xyy);
    cmsXYZ2Lab(cmsD50_XYZ(), &lab, &xyz);
    
    return TrueColor((float)lab.L, (float)lab.a, (float)lab.b);
}

void TrueColor::from_sRGB(const color* rgb, TrueColor* tc, size_t n)
{
    static cmsHTRANSFORM conv = nullptr;

    if (conv == nullptr)
    {
        // Absolute colorimetric intent causes sRGB white and grays to be
        // very slightly offset from CIELAB's center, does it matter?
        conv = cmsCreateTransformTHR(Impl::defaultContext,
            Impl::sRGB, TYPE_RGBA_FLT, Impl::LAB4, TYPE_LabA_FLT,
            INTENT_ABSOLUTE_COLORIMETRIC, Impl::tfFlags);
        if (conv == nullptr)
            throw runtime_error("unable to create transform");
        Impl::transforms.push_back(conv);
    }
    cmsDoTransform(conv, (void*)rgb, (void*)tc, n);
}

void TrueColor::to_sRGB(const TrueColor* tc, color* rgb, size_t n)
{
    static cmsHTRANSFORM conv = nullptr;
    if (conv == nullptr)
    {
        conv = cmsCreateTransformTHR(Impl::defaultContext,
            Impl::LAB4, TYPE_LabA_FLT, Impl::sRGB, TYPE_RGBA_FLT,
            INTENT_ABSOLUTE_COLORIMETRIC, Impl::tfFlags);
        if (conv == nullptr)
            throw runtime_error("unable to create transform");
        Impl::transforms.push_back(conv);
    }

    cmsDoTransform(conv, (void*)tc, (void*)rgb, n);
}

bool TrueColor::is_sRGB() const
{
    color srgb;
    to_sRGB(this, &srgb, 1);

    return srgb.r >= 0 && srgb.r <= 1 
        && srgb.g >= 0 && srgb.g <= 1 
        && srgb.b >= 0 && srgb.b <= 1;
}